package com.baomidou.mybatisplus.spring.annotation;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScannerRegistrar;
import org.mybatis.spring.annotation.MapperScans;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import com.baomidou.mybatisplus.spring.mapper.MapperEntityScanner;

import lombok.Setter;

/**
 * Reference {@link MapperScannerRegistrar}
 * @author Yangtze
 */
public class MapperEntityRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, ResourceLoaderAware {

	@Resource @Setter
	private SqlSessionFactory sqlSessionFactory;

	@Resource @Setter
	private SqlSessionTemplate sqlSessionTemplate;
	
	@Setter
	private ResourceLoader resourceLoader;
	
	@Setter
	private ClassLoader beanClassLoader;
	
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	    AnnotationAttributes mapperBeanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperEntityScan.class.getName()));

	    if (mapperBeanAttrs != null) {
	    	registerBeanDefinitions(mapperBeanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
	    }
	}
	
	void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry,
			String beanName) {
	    MapperEntityScanner scanner = new MapperEntityScanner(registry);	    
	    // this check is needed in Spring 3.1
	    Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);

	    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
	    if (!Annotation.class.equals(annotationClass)) {
	      scanner.setAnnotationClass(annotationClass);
	    }

	    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
	    if (!Class.class.equals(markerInterface)) {
	      scanner.setMarkerInterface(markerInterface);
	    }

	    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
	    if (!BeanNameGenerator.class.equals(generatorClass)) {
	      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
	    }

	    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
	    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
	      scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
	    }

	    scanner.setSqlSessionFactory(this.sqlSessionFactory);
	    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
	    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
	    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

	    List<String> basePackages = new ArrayList<>();
	    basePackages.addAll(
	        Arrays.stream(annoAttrs.getStringArray("value"))
	            .filter(StringUtils::hasText)
	            .collect(Collectors.toList()));
	    
	    scanner.setBeanClassLoader(beanClassLoader);

	    scanner.registerFilters();
	    scanner.doScan(StringUtils.toStringArray(basePackages));
	}

	private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
		return importingClassMetadata.getClassName() + "#" + MapperEntityRegistrar.class.getSimpleName() + "#" + index;
	}

	static class RepeatingRegistrar extends MapperEntityRegistrar {
		
	    @Override
	    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	      AnnotationAttributes mapperBeansAttrs = AnnotationAttributes
	          .fromMap(importingClassMetadata.getAnnotationAttributes(MapperEntityScans.class.getName()));
	      if (mapperBeansAttrs != null) {
	        AnnotationAttributes[] annotations = mapperBeansAttrs.getAnnotationArray("value");
	        for (int i = 0; i < annotations.length; i++) {
	          registerBeanDefinitions(annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));
	        }
	      }
	    }

	}

}
